#include "lzwencoder.h"
#include "bitencoder.h"
#include <QMap>

namespace WingGif {

LZWEncoder::LZWEncoder(QByteArray indexedPixel, int colorDepth, QObject *parent)
    : QObject(parent) {
  _indexedPixels = indexedPixel;
  colorDepth = qMax(2, colorDepth);
  initDataSize = colorDepth;
}

int LZWEncoder::colorDepth() { return _colorDepth; }

void LZWEncoder::setColorDepth(int value) { _colorDepth = value; }

QByteArray LZWEncoder::indexedPixels() { return _indexedPixels; }

void LZWEncoder::setIndexedPixels(QByteArray value) {
  _indexedPixels.swap(value);
}

void LZWEncoder::encode(QIODevice *internalStream) {
  // Validation

  if (!internalStream)
    throw "You need to provide a stream.";

  // If it's the first step.
  auto isFirst = true;

  auto clearFlag = (1 << _colorDepth);
  auto endOfFileFlag = clearFlag + 1;

  auto codeTable = new QMap<QString, int>();

  // Number of indexes of the currently processed bytes.
  auto releaseCount = 0;

  auto codeSize = uchar(_colorDepth + 1);
  auto availableCode = endOfFileFlag + 1;
  auto maskCode = (1 << codeSize) - 1;

  BitEncoder bitEncoder(codeSize, this);

  // Initial code size.
  internalStream->write(QByteArray(1, char(_colorDepth)));

  // First thing being added.
  bitEncoder.add(clearFlag);

  int suffix = 0;

  while (releaseCount < _indexedPixels.length()) {
    // If it's the first byte

    if (isFirst) {
      // The first time, the suffix is set to the first index bytes.
      suffix = _indexedPixels[releaseCount++];

      // If it's the last one.
      if (releaseCount == _indexedPixels.length()) {
        bitEncoder.add(suffix);
        bitEncoder.add(endOfFileFlag);
        bitEncoder.end();

        internalStream->write(QByteArray(1, char(bitEncoder.length())));
        internalStream->write(bitEncoder.outList);
        bitEncoder.outList.clear();
        break;
      }

      isFirst = false;
      continue;
    }

    // Before and after the change, and the constituent entities

    auto prefix = suffix;
    suffix = _indexedPixels[releaseCount++];
    QString entry = QString("%1,%2").arg(prefix, suffix);

    // If you do not know the current entity, entities encoded,
    // and output the prefix

    if (!codeTable->contains(entry)) {
      // If the current entity is not encoded, then output the prefix
      bitEncoder.add(prefix);

      // And the current entity is encoded. Inserts and after that adds the
      // availableCode count.
      codeTable->insert(entry, availableCode++);

      if (availableCode > (MaxStackSize - 3)) {
        // Insert the clear tag, reinvent
        codeTable->clear();
        _colorDepth = initDataSize;
        codeSize = uchar(_colorDepth + 1);
        availableCode = endOfFileFlag + 1;
        maskCode = (1 << codeSize) - 1;

        bitEncoder.add(clearFlag);
        bitEncoder.inBit = codeSize;
      } else if (availableCode > (1 << codeSize)) {
        // If the current code is greater than the current code available to
        // represent values
        _colorDepth++;
        codeSize = uchar(_colorDepth + 1);
        bitEncoder.inBit = codeSize;
        maskCode = (1 << codeSize) - 1;
      }

      // Divides into more blocks.
      if (bitEncoder.length() >= 255) {
        // Size of the block.
        internalStream->write(QByteArray(1, char(255)));
        // Writes the 255 sized block.
        internalStream->write(bitEncoder.outList);

        if (bitEncoder.length() > 255) {
          // Removes the last written 255 bytes.
          bitEncoder.outList = bitEncoder.outList.remove(0, 255);
        } else {
          bitEncoder.outList.clear();
        }
      }
    }

    // If you know the current entity,
    // set the suffixes to the current index value of an entity

    else {
      // Set the suffix to the current entity encoding
      suffix = codeTable->value(entry);
    }

    // if (releaseCount == 40240)
    //     suffix = suffix;

    // To the end of an image, writes over identity,
    // and outputs the current codes left in the data stream

    if (releaseCount == _indexedPixels.length()) {
      bitEncoder.add(suffix);        // Adds the last sufix.
      bitEncoder.add(endOfFileFlag); // End of the LZW
      bitEncoder.end();

      // If the block size if greater than 255, divides into two.
      if (bitEncoder.length() > 255) {

        bitEncoder.outList = bitEncoder.outList.remove(0, 255);
        internalStream->write(QByteArray(1, char(bitEncoder.length())));
        internalStream->write(bitEncoder.outList);
      } else {
        internalStream->write(QByteArray(1, char(bitEncoder.length())));
        internalStream->write(bitEncoder.outList);
        bitEncoder.outList.clear();
      }

      break;
    }
  }

  // For 3 weeks I forgot to add this little piece of sh*t, my gifs were always
  // corrupted... Signals the end of the list of blocks.
  internalStream->write(QByteArray(1, char(0)));
}

} // namespace WingGif
